// File:      erlymate.c
// author:    Roberto Saccon <rsaccon@gmail.com> [http://rsaccon.com]
// copyright: 2007 Roberto Saccon
//  
// parts of erlymate.c are derived from erlaunch.c source code.
// Original erlaunch.c license:
// ============================================================================
// erlaunch - execute selected Erlang functions from system command line
// Copyright (c)2003-2004 Cat's Eye Technologies.  All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 
//    Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 
//    Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in
//    the documentation and/or other materials provided with the
//    distribution.
// 
//    Neither the name of Cat's Eye Technologies nor the names of its
//    contributors may be used to endorse or promote products derived
//    from this software without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
// CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF AsDVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
// ============================================================================
// 
// the following license applies to all parts not covered by the above license:
// 
// The MIT License
// 
// Copyright (c) 2007 Roberto Saccon
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// 
// since 2007-12-01 by Roberto Saccon

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/param.h>
#include <unistd.h>
#include <err.h>
#include <errno.h>
#include <sysexits.h>


/* CONSTANTS */

#define ERLYMATE_VERSION "0.1"
#define ERLYMATE_DEFAULT_HOST "127.0.0.1"
#define ERLYMATE_DEFAULT_PORT 17779
#define ERLYMATE_DEFAULT_COOKIE "secretcookie"
#define ERLYMATE_HOST_SIZE 255
#define ERLYMATE_PORT_SIZE 31
#define ERLYMATE_COOKIE_SIZE 255
#define ERLYMATE_TERMSTRING_SIZE 4095

int port;
char host[ERLYMATE_HOST_SIZE];
char port_string[ERLYMATE_PORT_SIZE];
char cookie[ERLYMATE_COOKIE_SIZE];
char termstring[ERLYMATE_TERMSTRING_SIZE];
char pass_through[2];


/*
 * open_tcp: create a TCP/IP client socket and connect to the server.
 * returns a socket descriptor.
 */
int open_tcp(void)
{
    struct sockaddr_in tcp;
    int sd;
    if ((sd = socket(PF_INET, SOCK_STREAM, 0)) == -1)
    {
        err(EX_UNAVAILABLE, "socket");
    }
    bzero(&tcp, sizeof(tcp));
    tcp.sin_family = AF_INET;
    tcp.sin_port = htons(port);
    inet_aton(host, &tcp.sin_addr);
    if (connect(sd, (struct sockaddr *)&tcp, sizeof(tcp)) != 0)
    {
        err(EX_UNAVAILABLE, "connect");
    }
    return sd;
}


/*
 * close_tcp: close a client socket.
 */
void close_tcp(int sd)
{
    close(sd);
}


/*
 * send_string: send a string to the server via a client socket.
 *              The string is packetized, along with a 'not an eof' byte,
 *              by preceding it with its length (plus one for the byte)
 *              as a four-byte integer in network byte order.
 */
void send_string(int sd, char * string)
{
    long length = strlen(string) + 1;
    long nlength = htonl(length);
    char no_eof = 0;
    write(sd, &nlength, 4);
    write(sd, &no_eof, 1);
    write(sd, string, (length - 1));
}


/*
 * send_array: send an array of strings to the server via a client.
 *             Each string is sent a la send_string, followed by a
 *             zero-length string to indicate end of array.
 *             If 'size' parameter is zero, strings are sent until
 *             a NULL string is found.  Otherwise 'size' strings are sent.
 */
void send_array(int sd, int size, char * string[])
{
    int i;
    if (size == 0)
    {
        for(i = 0; string[i] != NULL; i++)
        {
            send_string(sd, string[i]);
        }
    } else {
        for(i = 0; i < size; i++)
        {           
            send_string(sd, string[i]);
        }
    }
    send_string(sd, "");
}


/*
 * send_eof: sends an end-of-file indicator to the server.
 */
void send_eof(int sd)
{
    long nlength = htonl(1);
    char yes_eof = 1;
    write(sd, &nlength, 4);
    write(sd, &yes_eof, 1);
}

/*
 * recv_packet: receives a packet on the client socket from the server.
 *              Packets from the server consist of a four-byte length
 *              sent in network byte order, followed by the data.
 *              The data consists of a 1-byte 'exit code' followed by
 *              a string of any length.
 */
long recv_packet(int sd, int maxsize, char * buffer, char * exit_code)
{
    long nlength;
    long length;
    read(sd, &nlength, 4);
    length = ntohl(nlength);
    if (length < maxsize)
    {
        read(sd, exit_code, 1);
        read(sd, buffer, length - 1);
        buffer[length - 1] = 0;
        return length - 1;
    } else {
        fprintf(stderr, "packet length %ld exceeded maxsize %d\n", length, maxsize);
        exit(EX_UNAVAILABLE);
    }
}


const char *escape_char(int ch)
{
    switch(ch)
    {
        case '\n':
            return "\\n"; 
            break;
        case '\t':
            return "\\t"; 
            break;
        case '\v':
            return "\\v"; 
            break;
        case '\b':
            return "\\b"; 
            break;
        case '\r':
            return "\\r"; 
            break;
        case '\f':
            return "\\f"; 
            break;
        case '\a':
            return "\\a"; 
            break;
        case '\\':
            return "\\\\"; 
            break;
        case '\'':
            return "\\\'"; 
            break;
        case '\"':
            return "\\\""; 
            break;
        default:
            sprintf(pass_through, "%c", ch);
            return pass_through;
            break;
    }
}


void escape_string(const char *in, char *out)
{
    int i;
    
    for (i = 0; in[i] != (char)NULL; i++)
    {
        strcat(out, escape_char(in[i]));
    }
}


void escape(const char *name, char *string)
{    
    if (getenv(name) != NULL) {        
        escape_string(getenv(name), string);
    } else {
        string = "";
    }
}


int run_cmd(int argc, char * argv[])
{
    char * command = argv[1];

    int sd;

    char string[4095];
    long slength;
    char exit_code = 0;

    char inchar;
    char inbuf[4095];
    int  inoff = 0;
    int  inerr = 0;
  
    fd_set readfds;
    fd_set writefds;
    fd_set exceptfds;

    sd = open_tcp();
   
    char tm_mode[255] = "";
    char tm_filepath[255] = "";
    char tm_current_line[4095] = "";
    char tm_current_word[255] = "";
    char tm_line_index[127] = "";
    char tm_line_number[127] = "";
    char tm_selected_text[4095] ="";

    escape("TM_MODE", tm_mode);
    escape("TM_FILEPATH", tm_filepath); 
    escape("TM_CURRENT_LINE", tm_current_line); 
    escape("TM_CURRENT_WORD", tm_current_word); 
    escape("TM_LINE_INDEX", tm_line_index);
    escape("TM_LINE_NUMBER", tm_line_number);
    escape("TM_SELECTED_TEXT", tm_selected_text);
 
    sprintf(termstring, "{\"%s\", \"%s\", \"%s\", \"%s\", %s, %s, \"%s\"}.", 
        tm_mode, 
        tm_filepath, 
        tm_current_line, 
        tm_current_word, 
        tm_line_index, 
        tm_line_number, 
        tm_selected_text);
         
    send_string(sd, cookie);
    send_string(sd, command);
    send_string(sd, termstring);
     
    recv_packet(sd, 4095, string, &exit_code);
    if (!exit_code)
    {
        send_array(sd, argc - 2, &(argv[2]));        
    }      
    
    FD_ZERO(&writefds);
    FD_ZERO(&exceptfds);

    while (exit_code == 0)
    {    
        FD_ZERO(&readfds);
        FD_SET(STDIN_FILENO, &readfds);
        FD_SET(sd, &readfds);
            
        select(FD_SETSIZE, &readfds, &writefds, &exceptfds, NULL);
        if (FD_ISSET(sd, &readfds))
        {
            slength = recv_packet(sd, 4095, string, &exit_code);
            write(STDOUT_FILENO, string, slength);
        }
        if (FD_ISSET(STDIN_FILENO, &readfds))
        {
            inerr = read(STDIN_FILENO, &inchar, 1);
            if (inerr > 0) {
                inbuf[inoff++] = inchar;
                if (inchar == '\n' || inoff > 4094) {
                    inbuf[inoff] = 0;
                    send_string(sd, inbuf);
                    inoff = 0;
                } else if (inerr == 0)
                    send_eof(sd);
            } else if (inerr < 0)
                err(EX_DATAERR, "read(STDIN)");
        }
    }  
    
    close_tcp(sd);
    return ((int)exit_code) - 1;
}

int main(int argc, char * argv[])
{     
    port = ERLYMATE_DEFAULT_PORT;
    strncpy(host, ERLYMATE_DEFAULT_HOST, ERLYMATE_HOST_SIZE);
    strncpy(cookie, ERLYMATE_DEFAULT_COOKIE, ERLYMATE_COOKIE_SIZE);
       
    return run_cmd(argc, argv);
}